﻿using DapperExtensions;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
using System;
using System.Data;

namespace BJoin.DapperExtension.DatabaseFactory
{
    public interface IFactory
    {
    }
    public interface IFactory<out T> : IFactory
    {
        T Create();
    }
    public interface IDbFactory<out T, TSharding> : IFactory<T>
    {
        T Create(ReadWrite readwrite);
        T Create(TSharding shardingKey);
        T Create(ReadWrite readwrite, TSharding shardingKey);
    }

    public enum ReadWrite
    {
        Write = 0,
        Read
    }

    public class DbFactoryOptions
    {
        public string WriteConnectionKey { get; set; }
        public string ReadConnectionKey { get; set; }
        public Func<string, IDbConnection> ConnectionCtor { get; set; }
    }

    /// <summary>
    /// 读写分离 多数据源 多租户
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <typeparam name="TSharding"></typeparam>
    public class DbFactory<T, TSharding> : IDbFactory<T, TSharding>
    {
        protected readonly IConfiguration _config;
        protected readonly DbFactoryOptions _dbFactoryOptions;
        protected readonly IReadConnectionSelector _readConnectionSelector;
        protected readonly Func<IDbConnection, T> _dbCtor;

        public DbFactory(IConfiguration config
            , IOptions<DbFactoryOptions> dbFactoryOptions
            , IReadConnectionSelector readConnectionSelector
            , Func<IDbConnection, T> dbCtor)
        {
            _config = config;
            _dbFactoryOptions = dbFactoryOptions.Value;
            _readConnectionSelector = readConnectionSelector;
            _dbCtor = dbCtor;
        }

        public virtual T Create()
        {
            return Create(ReadWrite.Write);
        }

        public virtual T Create(TSharding shardingKey)
        {
            return Create(ReadWrite.Write, shardingKey);
        }

        public virtual T Create(ReadWrite readwrite)
        {
            var connection = GetDbConnection(readwrite);
            return _dbCtor.Invoke(connection);
        }

        public virtual T Create(ReadWrite readwrite, TSharding shardingKey)
        {
            //1. hash(shardingKey) % 2 pow x -> hash
            //  shard_num = hash(_routing) % num_primary_shards
            //  shard_num = (hash(_routing) + hash(_id) % routing_partition_size) % num_primary_shards
            //2. select id, writeconn, readconn form db where id = shardingkey -> db
            //    owner_db 
            //      - biz_db_bj, biz_db_nx, biz_db_trial
            throw new NotImplementedException();
        }

        protected virtual IDbConnection GetDbConnection(ReadWrite readWrite)
        {
            string strConnection = GetDbConnectionString(readWrite);
            return InitialDbConnection(strConnection);
        }

        private string GetDbConnectionString(ReadWrite readWrite)
        {
            string strConnection;
            switch (readWrite)
            {
                case ReadWrite.Write:
                    strConnection = _config.GetConnectionString(_dbFactoryOptions.WriteConnectionKey);
                    break;
                case ReadWrite.Read:
                    var readConnStr = _config.GetConnectionString(_dbFactoryOptions.ReadConnectionKey);
                    var aryConnStr = readConnStr.Split(",", StringSplitOptions.RemoveEmptyEntries);
                    strConnection = _readConnectionSelector.SelectConnectionString(_config, aryConnStr);
                    break;
                default:
                    throw new NotSupportedException();
            }

            return strConnection;
        }

        protected virtual IDbConnection InitialDbConnection(string strConnection)
        {
            if (_dbFactoryOptions.ConnectionCtor == null)
            {
                throw new ArgumentException($"{nameof(_dbFactoryOptions.ConnectionCtor)} is null");
            }
            return _dbFactoryOptions.ConnectionCtor.Invoke(strConnection);
        }
    }

    public class DatabaseFactory : DbFactory<IDatabase, long?>
    {
        public DatabaseFactory(IConfiguration config
            , IOptions<DbFactoryOptions> dbFactoryOptions
            , IReadConnectionSelector readConnectionSelector
            , Func<IDbConnection, IDatabase> dbCtor)
            : base(config, dbFactoryOptions, readConnectionSelector, dbCtor)
        {

        }

        public override IDatabase Create(ReadWrite readwrite, long? shardingKey)
        {
            //暂时不分库
            return Create(readwrite);
        }
    }

    public interface IReadConnectionSelector
    {
        string SelectConnectionString(IConfiguration configuration, string[] aryConnStr);
    }

    public class ReadConnectionSelector : IReadConnectionSelector
    {
        private static readonly Random _random=new Random();
        private int _cur = -1;
        private readonly object _locker = new object();

        //dynamic => Register(key, hanlder)
        public string SelectConnectionString(IConfiguration configuration, string[] aryConnStr)
        {
            if (aryConnStr.Length <= 0) throw new ArgumentNullException(aryConnStr.ToString(), "read connection string not config, please check the config.");
            if (aryConnStr.Length == 1) return aryConnStr[0];
            var rule = configuration.GetValue<string>("ReadConnectionSelectRule");
            if ("Random".Equals(rule, StringComparison.OrdinalIgnoreCase))
            {
                return SelectRandom(aryConnStr);
            }
            else if ("Polling".Equals(rule, StringComparison.OrdinalIgnoreCase))
            {
                return SelectPolling(aryConnStr);
            }
            //默认策略
            return SelectRandom(aryConnStr);
        }

        /// <summary>
        /// 随机
        /// </summary>
        /// <param name="connstrs"></param>
        /// <returns></returns>
        private string SelectRandom(string[] connstrs)
        {
#pragma warning disable SCS0005 // Weak random generator
            var selected = _random.Next(0, connstrs.Length - 1);
#pragma warning restore SCS0005 // Weak random generator
            return connstrs[selected];
        }

        /// <summary>
        /// 轮询
        /// </summary>
        /// <returns></returns>
        private string SelectPolling(string[] connstrs)
        {
            lock (_locker)
            {
                ++_cur;
                if (_cur >= connstrs.Length) _cur = 0;
                return connstrs[_cur];
            }
        }
    }
}
